package org.codefilarete.stalactite.spring.repository.query.domain;

import java.util.List;
import java.util.function.Supplier;

import org.codefilarete.reflection.AccessorByMember;
import org.codefilarete.reflection.AccessorChain;
import org.codefilarete.reflection.Accessors;
import org.codefilarete.stalactite.engine.EntityCriteria.OrderByChain.Order;
import org.codefilarete.stalactite.engine.runtime.AdvancedEntityPersister;
import org.codefilarete.stalactite.engine.runtime.RelationalEntityPersister.ExecutableEntityQueryCriteria;
import org.codefilarete.stalactite.engine.runtime.query.EntityQueryCriteriaSupport;
import org.codefilarete.stalactite.engine.runtime.query.EntityQueryCriteriaSupport.EntityQueryPageSupport;
import org.codefilarete.stalactite.query.model.Limit;
import org.codefilarete.stalactite.spring.repository.query.execution.AbstractQueryExecutor;
import org.codefilarete.stalactite.spring.repository.query.StalactiteQueryMethod;
import org.codefilarete.stalactite.spring.repository.query.execution.StalactiteQueryMethodInvocationParameters;
import org.codefilarete.stalactite.spring.repository.query.derivation.ToCriteriaPartTreeTransformer;
import org.codefilarete.stalactite.spring.repository.query.derivation.ToCriteriaPartTreeTransformer.Condition;
import org.codefilarete.stalactite.sql.result.Accumulators;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.repository.query.parser.PartTree;

/**
 * Implementation of {@link AbstractQueryExecutor} dedicated to domain entities.
 * Implementation is based on {@link EntityQueryCriteriaSupport} provided by the {@link AdvancedEntityPersister}.
 *
 * @param <C> domain entity type
 * @author Guillaume Mary
 */
public class DomainEntityQueryExecutor<C> extends AbstractQueryExecutor<List<C>, C> {
	
	private final AdvancedEntityPersister<C, ?> entityPersister;
	private final ToCriteriaPartTreeTransformer<C> criteriaAppender;
	
	public DomainEntityQueryExecutor(StalactiteQueryMethod method,
									 AdvancedEntityPersister<C, ?> entityPersister,
									 PartTree partTree) {
		super(method);
		this.entityPersister = entityPersister;
		this.criteriaAppender = new ToCriteriaPartTreeTransformer<>(
				partTree,
				entityPersister.getClassToPersist());
	}
	
	@Override
	public Supplier<List<C>> buildQueryExecutor(StalactiteQueryMethodInvocationParameters invocationParameters) {
		return () -> {
			EntityQueryCriteriaSupport<C, ?> executableEntityQuery = entityPersister.newCriteriaSupport();
			Condition condition = criteriaAppender.applyTo(
					executableEntityQuery.getEntityCriteriaSupport(),
					executableEntityQuery.getQueryPageSupport(),
					executableEntityQuery.getQueryPageSupport());
			condition.consume(invocationParameters.getValues());
			
			ExecutableEntityQueryCriteria<C, ?> executableEntityQueryCriteria = handleDynamicParameters(invocationParameters, executableEntityQuery);
			
			List<C> adaptation = executableEntityQueryCriteria.execute(Accumulators.toList());
			return method.getResultProcessor().processResult(adaptation);
		};
	}
	
	private ExecutableEntityQueryCriteria<C, ?> handleDynamicParameters(StalactiteQueryMethodInvocationParameters invocationParameters,
																		EntityQueryCriteriaSupport<C, ?> defaultExecutableEntityQuery) {
		EntityQueryCriteriaSupport<C, ?> derivedQueryToUse;
		// following code will manage both Sort as an argument, and Sort in a Pageable because getSort() handle both
		if (invocationParameters.getSort().isSorted()) {
			Class<?> declaringClass = method.getEntityInformation().getJavaType();
			// Spring Sort class supports only first-level properties, in-depth ones seems not to be definable,
			// therefore we create AccessorChain of only one property
			EntityQueryPageSupport<C> dynamicSortSupport = new EntityQueryPageSupport<>();
			invocationParameters.getSort().stream().forEachOrdered(order -> {
				AccessorByMember<?, ?, ?> accessor = Accessors.accessor(declaringClass, order.getProperty());
				dynamicSortSupport.orderBy(new AccessorChain<>(accessor),
						order.getDirection() == Direction.ASC ? Order.ASC : Order.DESC,
						order.isIgnoreCase());
			});
			derivedQueryToUse = defaultExecutableEntityQuery.copyFor(dynamicSortSupport);
		} else {
			derivedQueryToUse = defaultExecutableEntityQuery;
		}
		ExecutableEntityQueryCriteria<C, ?> result = derivedQueryToUse.wrapIntoExecutable();
		Limit limit = invocationParameters.getLimit();
		if (limit != null) {
			result.limit(limit.getCount(), limit.getOffset());
		}
		return result;
	}
}
